/*
 * Copyright (c) 2007 centrevillage <centrevillage@gmail.com>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
// std
#include <vector>

// seq
#include "def.h"
#include "MidiDevice.h"
#include "MidiMsg.h"

namespace seq {

MidiDevice::MidiDevice() : 
    hMidiOut_(0), 
    devID_(-2),
    isLocked_(false),
    pMidiHdr_(0)
{}

MidiDevice::MidiDevice(UINT32 devID) 
{
    MidiDevice::MidiDevice();
    setup(devID);
}

MidiDevice::~MidiDevice(){}

void MidiDevice::setup(UINT32 devID) {devID_ = devID;}

int MidiDevice::getDeviceID() {
    return devID_;
}

int MidiDevice::open() {
    if (hMidiOut_ != 0) return -1;
    if (devID_ == -2) return -3;
    int result;

    result = midiOutOpen(
            &hMidiOut_,
            devID_,
            NULL,
            NULL,
            CALLBACK_NULL);

    switch (result) {
        case MMSYSERR_NOERROR:
            return 0;
        case MMSYSERR_ALLOCATED:
        case MMSYSERR_BADDEVICEID:
        case MMSYSERR_INVALPARAM:
        case MMSYSERR_NOMEM:
            hMidiOut_ = 0;
            return -2;
    }
    return 0;
}

int MidiDevice::close() {
    if (hMidiOut_ == 0) return 0;

    MMRESULT result;

    result = midiOutClose(hMidiOut_);
    hMidiOut_ = 0;

    switch (result) {
        case MMSYSERR_NOERROR:
            return 0;
        case MIDIERR_STILLPLAYING:
        case MMSYSERR_INVALHANDLE:
        case MMSYSERR_INVALPARAM:
        case MMSYSERR_NOMEM:
            return -2;
    }
    return 0;
}

int MidiDevice::sendMsg(const MidiMsg& msg) {
    if (hMidiOut_ == 0) return -1;

    MMRESULT result;
    if (msg.isLong()) {
        if (pMidiHdr_ != 0) return -3;
		const std::vector<BYTE>& msgList = msg.getMsgList();
        BYTE* pData = new BYTE[msgList.size()];
        for (unsigned i = 0; i < msgList.size(); ++i) {
            pData[i] = msgList[i];
        }

        pMidiHdr_ = new MIDIHDR;
        memset(pMidiHdr_, '\0', sizeof(MIDIHDR));
        pMidiHdr_->lpData = (LPSTR)pData;
        pMidiHdr_->dwBufferLength = msgList.size();

        result = ::midiOutPrepareHeader(hMidiOut_, pMidiHdr_, sizeof(MIDIHDR));
        if (result != MMSYSERR_NOERROR) return -4;
        result = midiOutLongMsg(hMidiOut_, pMidiHdr_, sizeof(MIDIHDR));
        if (result != MMSYSERR_NOERROR) return -5;
        isLocked_ = true;
    } else {
        result = ::midiOutShortMsg(hMidiOut_, msg.getMsg());

        switch(result) {
            case MMSYSERR_NOERROR:
                return 0;
            case MIDIERR_BADOPENMODE:
            case MIDIERR_NOTREADY:
            case MMSYSERR_INVALHANDLE:
                return -2;
        }
    }
    return 0;
}

bool MidiDevice::isOpen() {return hMidiOut_ != 0;}

bool MidiDevice::tryRelease() {
    if (!isLocked_) return true;
	if (pMidiHdr_ == 0) return false;
	if (!(pMidiHdr_->dwFlags & MHDR_DONE)) return false;

	MMRESULT result = ::midiOutUnprepareHeader(hMidiOut_, pMidiHdr_, sizeof(MIDIHDR));

	if (result != MMSYSERR_NOERROR) return false;

    if (pMidiHdr_->lpData != 0) {
        delete [] pMidiHdr_->lpData;
    }
    delete pMidiHdr_;
    pMidiHdr_ = 0;
    isLocked_ = false;

	return true;
}

} // end of namespace seq
